/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.valves;

import org.apache.catalina.*;
import org.apache.catalina.comet.CometEvent;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.mbeans.MBeanUtils;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.juli.logging.Log;
import org.apache.tomcat.util.res.StringManager;

import javax.servlet.ServletException;
import java.io.IOException;

/**
 * Convenience base class for implementations of the <b>Valve</b> interface.
 * A subclass <strong>MUST</strong> implement an <code>invoke()</code>
 * method to provide the required functionality, and <strong>MAY</strong>
 * implement the <code>Lifecycle</code> interface to provide configuration
 * management and lifecycle support.
 *
 * @author Craig R. McClanahan
 */
public abstract class ValveBase extends LifecycleMBeanBase
		implements Contained, Valve {

	//------------------------------------------------------ Constructor

	/**
	 * Descriptive information about this Valve implementation.  This value
	 * should be overridden by subclasses.
	 */
	protected static final String info =
			"org.apache.catalina.core.ValveBase/1.0";
	/**
	 * The string manager for this package.
	 */
	protected static final StringManager sm =
			StringManager.getManager(Constants.Package);

	//------------------------------------------------------ Instance Variables
	/**
	 * Does this valve support Servlet 3+ async requests?
	 */
	protected boolean asyncSupported;

	/**
	 * The Container whose pipeline this Valve is a component of.
	 */
	protected Container container = null;

	/**
	 * Container log
	 */
	protected Log containerLog = null;
	/**
	 * The next Valve in the pipeline this Valve is a component of.
	 */
	protected Valve next = null;

	public ValveBase() {
		this(false);
	}

	public ValveBase(boolean asyncSupported) {
		this.asyncSupported = asyncSupported;
	}


	//-------------------------------------------------------------- Properties

	/**
	 * Return the Container with which this Valve is associated, if any.
	 */
	@Override
	public Container getContainer() {

		return (container);

	}

	/**
	 * Set the Container with which this Valve is associated, if any.
	 *
	 * @param container The new associated container
	 */
	@Override
	public void setContainer(Container container) {

		this.container = container;

	}

	@Override
	public boolean isAsyncSupported() {
		return asyncSupported;
	}

	public void setAsyncSupported(boolean asyncSupported) {
		this.asyncSupported = asyncSupported;
	}

	/**
	 * Return descriptive information about this Valve implementation.
	 */
	@Override
	public String getInfo() {

		return (info);

	}

	/**
	 * Return the next Valve in this pipeline, or <code>null</code> if this
	 * is the last Valve in the pipeline.
	 */
	@Override
	public Valve getNext() {

		return (next);

	}

	/**
	 * Set the Valve that follows this one in the pipeline it is part of.
	 *
	 * @param valve The new next valve
	 */
	@Override
	public void setNext(Valve valve) {

		this.next = valve;

	}


	//---------------------------------------------------------- Public Methods

	/**
	 * Execute a periodic task, such as reloading, etc. This method will be
	 * invoked inside the classloading context of this container. Unexpected
	 * throwables will be caught and logged.
	 */
	@Override
	public void backgroundProcess() {
		// NOOP by default
	}

	/**
	 * The implementation-specific logic represented by this Valve.  See the
	 * Valve description for the normal design patterns for this method.
	 * <p>
	 * This method <strong>MUST</strong> be provided by a subclass.
	 *
	 * @param request  The servlet request to be processed
	 * @param response The servlet response to be created
	 * @throws IOException      if an input/output error occurs
	 * @throws ServletException if a servlet error occurs
	 */
	@Override
	public abstract void invoke(Request request, Response response)
			throws IOException, ServletException;

	/**
	 * Process a Comet event. This method will rarely need to be provided by
	 * a subclass, unless it needs to reassociate a particular object with
	 * the thread that is processing the request.
	 *
	 * @param request  The servlet request to be processed
	 * @param response The servlet response to be created
	 * @throws IOException      if an input/output error occurs, or is thrown
	 *                          by a subsequently invoked Valve, Filter, or Servlet
	 * @throws ServletException if a servlet error occurs, or is thrown
	 *                          by a subsequently invoked Valve, Filter, or Servlet
	 */
	@Override
	public void event(Request request, Response response, CometEvent event)
			throws IOException, ServletException {
		// Perform the request
		getNext().event(request, response, event);
	}

	@Override
	protected void initInternal() throws LifecycleException {
		super.initInternal();

		containerLog = getContainer().getLogger();
	}

	/**
	 * Start this component and implement the requirements
	 * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
	 *
	 * @throws LifecycleException if this component detects a fatal error
	 *                            that prevents this component from being used
	 */
	@Override
	protected synchronized void startInternal() throws LifecycleException {

		setState(LifecycleState.STARTING);
	}

	/**
	 * Stop this component and implement the requirements
	 * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
	 *
	 * @throws LifecycleException if this component detects a fatal error
	 *                            that prevents this component from being used
	 */
	@Override
	protected synchronized void stopInternal() throws LifecycleException {

		setState(LifecycleState.STOPPING);
	}

	/**
	 * Return a String rendering of this object.
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(this.getClass().getName());
		sb.append('[');
		if (container == null) {
			sb.append("Container is null");
		} else {
			sb.append(container.getName());
		}
		sb.append(']');
		return sb.toString();
	}

	// -------------------- JMX and Registration  --------------------
	@Override
	public String getObjectNameKeyProperties() {
		StringBuilder name = new StringBuilder("type=Valve");

		Container container = getContainer();

		name.append(MBeanUtils.getContainerKeyProperties(container));

		int seq = 0;

		// Pipeline may not be present in unit testing
		Pipeline p = container.getPipeline();
		if (p != null) {
			for (Valve valve : p.getValves()) {
				// Skip null valves
				if (valve == null) {
					continue;
				}
				// Only compare valves in pipeline until we find this valve
				if (valve == this) {
					break;
				}
				if (valve.getClass() == this.getClass()) {
					// Duplicate valve earlier in pipeline
					// increment sequence number
					seq++;
				}
			}
		}

		if (seq > 0) {
			name.append(",seq=");
			name.append(seq);
		}

		String className = this.getClass().getName();
		int period = className.lastIndexOf('.');
		if (period >= 0) {
			className = className.substring(period + 1);
		}
		name.append(",name=");
		name.append(className);

		return name.toString();
	}

	@Override
	public String getDomainInternal() {
		return MBeanUtils.getDomain(getContainer());
	}
}
